package lock

import (
	"database/sql"
	"gitee.com/tziye/renai-go/common"
	"gitee.com/tziye/renai-go/renai"
	"gitee.com/tziye/renai-go/sqlproxy"
	"github.com/google/uuid"
	"log"
)

type LockOperator struct {
	RenaiClient *renai.RenaiClient
	Db          *sql.DB
}

func NewLockOperator(renaiClient *renai.RenaiClient) *LockOperator {
	return &LockOperator{
		RenaiClient: renaiClient,
		Db:          renaiClient.Db,
	}
}

func (lockOperator *LockOperator) Lock(key string, value string, expireSeconds int) bool {
	return lockOperator.commonLock(key, value, expireSeconds)
}

func (lockOperator *LockOperator) TryLock(key string, value string, expireSeconds int, timeout int) bool {
	for waitSecond := 0; waitSecond < timeout; waitSecond++ {
		if lockOperator.commonLock(key, value, expireSeconds) {
			return true
		}
		common.Sleep(1)
	}
	return false
}

func (lockOperator *LockOperator) commonLock(key string, value string, expireSeconds int) bool {
	var updated sql.NullInt32
	row := sqlproxy.CallRow(lockOperator.Db, common.LOCK_LOCK, key, value, expireSeconds)
	err := row.Scan(&updated)
	if err != nil {
		common.LogErr(err)
		return false
	}
	return updated.Int32 > 0
}

func (lockOperator *LockOperator) Unlock(key string, value string) bool {
	s := "DELETE FROM renai_lock_record WHERE k = ? AND v = ?"
	rows := sqlproxy.Exec(lockOperator.Db, s, key, value)
	return rows > 0
}

func (lockOperator *LockOperator) Delete(keys []string) int {
	s := "DELETE FROM renai_lock_record WHERE k IN ("
	params := make([]any, 0)
	for i := 0; i < len(keys)-1; i++ {
		s += "?,"
		params = append(params, keys[i])
	}
	s += "?)"
	params = append(params, keys[len(keys)-1])
	rows := sqlproxy.Exec(lockOperator.Db, s, params...)
	return rows
}

type RenaiLock struct {
	LockOperator  *LockOperator
	Key           string
	Value         string
	ExpireSeconds int
	Timeout       int
	Locked        bool
}

func NewRenaiLock(lockOperator *LockOperator, key string, expireSeconds int, timeout int) *RenaiLock {
	return &RenaiLock{
		LockOperator:  lockOperator,
		Key:           key,
		Value:         uuid.New().String(),
		ExpireSeconds: expireSeconds,
		Timeout:       timeout,
		Locked:        false,
	}
}

func (renaiLock *RenaiLock) Lock() {
	if renaiLock.Timeout <= 0 {
		renaiLock.Locked = renaiLock.LockOperator.Lock(renaiLock.Key, renaiLock.Value, renaiLock.ExpireSeconds)
	} else {
		renaiLock.Locked = renaiLock.LockOperator.TryLock(renaiLock.Key, renaiLock.Value, renaiLock.ExpireSeconds, renaiLock.Timeout)
	}
}

func (renaiLock *RenaiLock) Unlock() bool {
	result := renaiLock.LockOperator.Unlock(renaiLock.Key, renaiLock.Value)
	renaiLock.Locked = result
	log.Println("Unlock")
	return result
}

// Lock

func Lock(renaiClient *renai.RenaiClient, key string, expireSeconds int) *RenaiLock {
	lockOperator := NewLockOperator(renaiClient)
	renaiLock := NewRenaiLock(lockOperator, key, expireSeconds, 0)
	renaiLock.Lock()
	return renaiLock
}

func TryLock(renaiClient *renai.RenaiClient, key string, expireSeconds int, timeout int) *RenaiLock {
	lockOperator := NewLockOperator(renaiClient)
	renaiLock := NewRenaiLock(lockOperator, key, expireSeconds, timeout)
	renaiLock.Lock()
	return renaiLock
}

// Executor

type Supplier interface {
	Get(params ...any) any
}

func Execute(renaiClient *renai.RenaiClient, key string, expireSeconds int, supplier Supplier, params ...any) any {
	return commonExecute(renaiClient, key, expireSeconds, 0, true, supplier, params...)
}

func ExecuteNoRelease(renaiClient *renai.RenaiClient, key string, expireSeconds int, supplier Supplier, params ...any) any {
	return commonExecute(renaiClient, key, expireSeconds, 0, false, supplier, params...)
}

func TryExecute(renaiClient *renai.RenaiClient, key string, expireSeconds int, timeout int, supplier Supplier, params ...any) any {
	return commonExecute(renaiClient, key, expireSeconds, timeout, true, supplier, params...)
}

func TryExecuteNoRelease(renaiClient *renai.RenaiClient, key string, expireSeconds int, timeout int, supplier Supplier, params ...any) any {
	return commonExecute(renaiClient, key, expireSeconds, timeout, false, supplier, params...)
}

func commonExecute(renaiClient *renai.RenaiClient, key string, expireSeconds int, timeout int, release bool, supplier Supplier, params ...any) any {
	var lock *RenaiLock
	if timeout <= 0 {
		lock = Lock(renaiClient, key, expireSeconds)
	} else {
		lock = TryLock(renaiClient, key, expireSeconds, timeout)
	}
	if !lock.Locked {
		var zero any
		return zero
	}
	if release {
		defer lock.Unlock()
	}
	return supplier.Get(params...)
}
